﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Linq.Expressions;

namespace Isuka.Linq
{
    /// <summary>
    /// クエリー
    /// http://ufcpp.net/study/csharp/source/Query.cs
    /// からコピー
    /// </summary>
    public class Query<T>
        : IOrderedQueryable<T>, IQueryable<T>, IEnumerable<T>
    {
        readonly QueryProvider provider;
        readonly Expression expression;

        /// <summary>
        /// コンストラクター
        /// </summary>
        /// <param name="provider"></param>
        public Query(QueryProvider provider)
        {
            Contract.Requires<ArgumentNullException>(provider != null, "provider");

            this.provider = provider;
            this.expression = Expression.Constant(this);
        }

        /// <summary>
        /// コンストラクター
        /// </summary>
        /// <param name="provider"></param>
        /// <param name="expression"></param>
        public Query(QueryProvider provider, Expression expression)
        {
            Contract.Requires<ArgumentNullException>(provider != null, "provider");
            Contract.Requires<ArgumentNullException>(expression != null, "expression");

            if (typeof(IQueryable<T>).IsAssignableFrom(expression.Type))
            {
                throw new ArgumentOutOfRangeException("expression");
            }

            this.provider = provider;
            this.expression = expression;
        }

        #region ContractInvariantMethod

        [ContractInvariantMethod]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")]
        private void ObjectInvariant()
        {
            Contract.Invariant(provider != null);
            Contract.Invariant(expression != null);
        }

        #endregion

        #region IEnumerable<T> メンバー

        public IEnumerator<T> GetEnumerator()
        {
            var e = provider.Execute(this.expression) as IEnumerable<T>;
            return Gate.NotNull(e).GetEnumerator();
        }

        #endregion

        #region IEnumerable メンバー

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            var e = provider.Execute(this.expression) as System.Collections.IEnumerable;
            return Gate.NotNull(e).GetEnumerator();
        }

        #endregion

        #region IQueryable メンバー

        public Type ElementType
        {
            get { return typeof(T); }
        }

        public System.Linq.Expressions.Expression Expression
        {
            get { return expression; }
        }

        public IQueryProvider Provider
        {
            get { return provider; }
        }

        #endregion
    }
}
